package com.shelpe.services.sinbad.controller;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.shelpe.services.sinbad.dao.WriteKeywordMapper;
import com.shelpe.services.sinbad.entity.jpa.Adgroup;
import com.shelpe.services.sinbad.entity.jpa.Campaign;
import com.shelpe.services.sinbad.entity.jpa.KeywordsLog;
import com.shelpe.services.sinbad.entity.jpa.User;
import com.shelpe.services.sinbad.entity.mongo.KeywordAddErrorLog;
import com.shelpe.services.sinbad.model.Keyword;
import com.shelpe.services.sinbad.model.KeywordExample;
import com.shelpe.services.sinbad.model.RptKeyword;
import com.shelpe.services.sinbad.model.RptKeywordExample;
import com.shelpe.services.sinbad.model.RptKeywordExample.Criteria;
import com.shelpe.services.sinbad.readdao.ReadRptKeywordMapper;
import com.shelpe.services.sinbad.repository.jpa.AdgroupRepository;
import com.shelpe.services.sinbad.repository.jpa.CampaignRepository;
import com.shelpe.services.sinbad.repository.jpa.KeywordsLogRepository;
import com.shelpe.services.sinbad.repository.jpa.UserRepository;
import com.shelpe.services.sinbad.repository.mongo.KeywordAddErrorLogRepository;
import com.shelpe.services.sinbad.service.impl.OopKeywordService;
import com.shelpe.services.sinbad.service.impl.OopKeywordService.SearchSettings;
import com.shelpe.services.sinbad.service.impl.SupplementService;
import com.shelpe.services.sinbad.service.simbaproxy.SegmentClient;
import com.shelpe.services.sinbad.service.simbaproxy.SimbaProxyBaseDto;
import com.shelpe.services.sinbad.utils.WordUtils;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("/sinbadservice")
@Api(value = "关键词相关的API", consumes = "application/json", produces = "application/json")
public class KeywordsController {

	@Autowired
	private UserRepository userRepository;
	@Autowired
	private AdgroupRepository adgroupRepository;
	@Autowired
	private CampaignRepository campaignRepository;
	@Autowired
	private OopKeywordService keywordService;
	@Autowired
	private WriteKeywordMapper keywordMapper;
	@Autowired
	private ReadRptKeywordMapper rptKeywordMapper;
	@Autowired
	private KeywordAddErrorLogRepository keywordAddErrorLogRepository;
	@Autowired
	private KeywordsLogRepository keywordsLogRepository;
	@Autowired
	private SegmentClient segmentClient;
	@Autowired
	private SupplementService supplementService;

	@ApiOperation(value = "推广组下关键词的获取")
	@RequestMapping(method = RequestMethod.GET, value = "/adgroups/{adgroup_id}/listKeywords")
	@ResponseBody
	public List<Keyword> getKeywords(@PathVariable("adgroup_id") long adgroupID) {
		List<Keyword> dtos = new ArrayList<Keyword>();
		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		Assert.notNull(adgroup, "invalid_adgroup");
		KeywordExample ex = new KeywordExample();
		ex.setUserId(adgroup.getUserID());
		ex.createCriteria().andAdgroupIdEqualTo(adgroupID);
		List<Keyword> keywords = this.keywordMapper.selectByExample(ex);
		for (Keyword keyword : keywords) {
			dtos.add(keyword);
		}
		return dtos;
	}

	/**
	 * 关键词出价
	 */
	@ApiOperation(value = "关键词调价")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "action_types", defaultValue = "auto", allowableValues = "auto,manual,semiauto", paramType = "query", dataType = "string", allowMultiple = true) })
	@RequestMapping(method = RequestMethod.POST, value = "/users/{user_id}/keywords/@pricing")
	@ResponseBody
	public DeferredResult<ListResponse<PricingKeyword>> pricingKeywords(@PathVariable("user_id") final long userID,
			@RequestParam(name = "action_types", defaultValue = "auto") List<String> actionTypes,
			@RequestBody List<PricingKeyword> keywords) {
		log.info("pricingKeywords: userID - {}", userID);
		final long start = System.currentTimeMillis();

		final ListResponse<PricingKeyword> ret = new ListResponse<PricingKeyword>();
		final DeferredResult<ListResponse<PricingKeyword>> result = new DeferredResult<ListResponse<PricingKeyword>>();
		result.onTimeout(new Runnable() {
			@Override
			public void run() {
				ListResponse<PricingKeyword> timeoutRet = new ListResponse<PricingKeyword>();
				timeoutRet.setError(new SimbaProxyBaseDto.Error());
				timeoutRet.getError().setCode(500);
				timeoutRet.getError().setMessage("timeout");
				timeoutRet.getError().setSubCode("timeout");
				timeoutRet.getError().setSubMessage("超时");
				timeoutRet.form(timeoutRet.getError());
				result.setErrorResult(timeoutRet);
				log.error("timeout pricingKeywords {}", userID);
			}
		});

		if (keywords == null || keywords.size() == 0) {
			ret.setStatus(false);
			result.setResult(ret);
			return result;
		}

		// 纠正一些错误输入
		List<PricingKeyword> needPricingKeywords = new ArrayList<PricingKeyword>();
		for (PricingKeyword pkw : keywords) {
			if (pkw.getMaxMobilePrice() > 0 && pkw.getMobileIsDefaultPrice() != 0) {
				pkw.setMobileIsDefaultPrice(0);
			}
			if (pkw.getMaxMobilePrice() <= 0 && pkw.getMobileIsDefaultPrice() != 1) {
				pkw.setMobileIsDefaultPrice(1);
				pkw.setMaxMobilePrice(0);
			}
			if (pkw.getIsDefaultPrice() == 1) {
				pkw.setIsDefaultPrice(0);
			}
			needPricingKeywords.add(pkw);
		}

		if (needPricingKeywords == null || needPricingKeywords.size() == 0) {
			ret.setStatus(false);
			result.setResult(ret);
			return result;
		}

		User user = this.userRepository.findOne(userID);
		Assert.notNull(user, "user_not_found");
		boolean isManual = false;
		boolean isSemiauto = false;
		if (actionTypes != null) {
			isManual = actionTypes.indexOf("manual") > -1;
			isSemiauto = actionTypes.indexOf("semiauto") > -1;
		}
		this.keywordService.price(user, needPricingKeywords, new OopKeywordService.Callback() {
			@Override
			public void call(List<Keyword> savedKeywords) {
				ret.setDatas(new ArrayList<PricingKeyword>());
				if (savedKeywords != null) {
					for (Keyword kw : savedKeywords) {
						PricingKeyword pkw = new PricingKeyword();
						pkw.setKeywordID(kw.getKeywordId());
						pkw.setWord(kw.getWord());
						pkw.setMatchScope(kw.getMatchscope());
						pkw.setMaxPrice(kw.getMaxPrice());
						pkw.setIsDefaultPrice(0);
						pkw.setMaxMobilePrice(kw.getMaxMobilePrice());
						pkw.setMobileIsDefaultPrice(kw.getMobileIsDefaultPrice());
						ret.getDatas().add(pkw);
					}
				}
				log.info("pricingKeywords costs {} ms", (System.currentTimeMillis() - start));
				ret.setStatus(ret.getDatas().size() > 0);
				result.setResult(ret);
			}
		}, isManual, isSemiauto);
		return result;
	}

	/**
	 * 添加关键词
	 * 
	 * @param adgroupID
	 * @param keywords
	 * @return
	 */
	@ApiOperation(value = "添加关键词")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "action_type", defaultValue = "auto", allowableValues = "auto,manual,semiauto", paramType = "query", dataType = "string") })
	@RequestMapping(method = RequestMethod.POST, value = "/adgroups/{adgroupId}/keywords")
	@ResponseBody
	public DeferredResult<ListResponse<PricingKeyword>> addKeywords(@PathVariable("adgroupId") final long adgroupID,
			@RequestParam(name = "action_type", defaultValue = "auto") String actionType,
			@RequestBody List<PricingKeyword> keywords) {
		log.info("addKeywords: adgroupID - {}", adgroupID);
		final long start = System.currentTimeMillis();

		final DeferredResult<ListResponse<PricingKeyword>> result = new DeferredResult<ListResponse<PricingKeyword>>();
		final ListResponse<PricingKeyword> ret = new ListResponse<PricingKeyword>();

		result.onTimeout(new Runnable() {
			@Override
			public void run() {
				log.error("timeout addKeywords {} {}", adgroupID);
				ListResponse<PricingKeyword> timeoutRet = new ListResponse<PricingKeyword>();
				timeoutRet.setError(new SimbaProxyBaseDto.Error());
				timeoutRet.getError().setCode(500);
				timeoutRet.getError().setMessage("timeout");
				timeoutRet.getError().setSubCode("timeout");
				timeoutRet.getError().setSubMessage("超时");
				timeoutRet.form(timeoutRet.getError());
				result.setErrorResult(timeoutRet);
			}
		});

		if (keywords == null || keywords.size() == 0) {
			ret.setStatus(false);
			result.setResult(ret);
			return result;
		}

		Adgroup adgroup = adgroupRepository.findOne(adgroupID);
		if (adgroup == null) {
			ret.setStatus(false);
			result.setResult(ret);
			return result;
		}

		// 纠正一些错误输入
		for (PricingKeyword pkw : keywords) {
			if (pkw.getMatchScope() <= -1) {
				pkw.setMatchScope(4);
			}
			if (pkw.getMobileIsDefaultPrice() > 0 && pkw.getMobileIsDefaultPrice() != 0) {
				pkw.setMobileIsDefaultPrice(0);
			}
			/*
			 * 适应淘宝api调整  关键词
			 * 入参做了调整isDefaultPrice、mobileIsDefaultPrice恒等于0，不能再等于1
			 */
			pkw.setIsDefaultPrice(0);
			pkw.setMobileIsDefaultPrice(0);
		}

		this.keywordService.add(adgroup, keywords, new OopKeywordService.Callback() {
			@Override
			public void call(List<Keyword> savedKeywords) {
				ret.setDatas(new ArrayList<PricingKeyword>());
				if (savedKeywords != null) {
					for (Keyword kw : savedKeywords) {
						PricingKeyword pkw = new PricingKeyword();
						pkw.setKeywordID(kw.getKeywordId());
						pkw.setWord(kw.getWord());
						pkw.setMatchScope(kw.getMatchscope());
						pkw.setMaxPrice(kw.getMaxPrice());
						pkw.setIsDefaultPrice(0);
						pkw.setMaxMobilePrice(kw.getMaxMobilePrice());
						pkw.setMobileIsDefaultPrice(kw.getMobileIsDefaultPrice());
						ret.getDatas().add(pkw);
					}
				}
				// if (ret.getDatas().size() != keywords.size()) {
				// log.info("there are some words to fail: {}", adgroup);
				// if ("auto".equals(actionType)) {
				// supplementService.supplementAdd(adgroup, ret.getDatas(),
				// keywords);
				// }
				// }
				log.info("addKeywords costs {} ms: {},keywords:{}", (System.currentTimeMillis() - start), adgroup,
						ret.getDatas().size());
				ret.setStatus(ret.getDatas().size() > 0);
				result.setResult(ret);
			}
		}, "manual".equals(actionType));
		return result;
	}

	/**
	 * 删除关键词
	 * 
	 * @param campaignId
	 * @param keywordIDs
	 * @return
	 */
	@ApiOperation(value = "删除关键词")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "action_types", defaultValue = "auto", allowableValues = "auto,manual,semiauto", paramType = "query", dataType = "string", allowMultiple = true) })
	@RequestMapping(method = RequestMethod.DELETE, path = "/campaigns/{campaign_id}/keywords")
	@ResponseBody
	public DeferredResult<ListResponse<PricingKeyword>> deleteKeywords(@PathVariable("campaign_id") long campaignID,
			@RequestParam(name = "action_types", defaultValue = "auto") List<String> actionTypes,
			@RequestParam(name = "keywordIds", required = false) List<Long> keywordIDs,
			@RequestBody(required = false) DeleteKeywords idsDto) {
		log.info("deleteKeywords: campaignID - {} - {} - {}", campaignID, keywordIDs, idsDto);
		final long start = System.currentTimeMillis();

		final DeferredResult<ListResponse<PricingKeyword>> result = new DeferredResult<ListResponse<PricingKeyword>>();
		final ListResponse<PricingKeyword> ret = new ListResponse<PricingKeyword>();

		result.onTimeout(new Runnable() {
			@Override
			public void run() {
				log.error("timeout deleteKeywords {} {}", campaignID);
				ListResponse<PricingKeyword> timeoutRet = new ListResponse<PricingKeyword>();
				timeoutRet.setError(new SimbaProxyBaseDto.Error());
				timeoutRet.getError().setCode(500);
				timeoutRet.getError().setMessage("timeout");
				timeoutRet.getError().setSubCode("timeout");
				timeoutRet.getError().setSubMessage("超时");
				timeoutRet.form(timeoutRet.getError());
				result.setErrorResult(timeoutRet);
			}
		});

		if ((keywordIDs == null || keywordIDs.size() == 0) && idsDto != null) {
			keywordIDs = idsDto.getKeywordIDs();
		}

		if (keywordIDs == null || keywordIDs.size() == 0) {
			ret.setStatus(false);
			result.setResult(ret);
			return result;
		}

		Campaign campaign = this.campaignRepository.findOne(campaignID);
		if (campaign == null) {
			ret.setStatus(false);
			result.setResult(ret);
			return result;
		}

		List<Long> ids = new ArrayList<Long>();
		for (long id : keywordIDs) {
			ids.add(id);
		}
		boolean isManual = false;
		boolean isSemiauto = false;
		if (actionTypes != null) {
			isManual = actionTypes.indexOf("manual") > -1;
			isSemiauto = actionTypes.indexOf("semiauto") > -1;
		}
		this.keywordService.delete(campaign, ids, new OopKeywordService.Callback() {
			@Override
			public void call(List<Keyword> savedKeywords) {
				ret.setDatas(new ArrayList<PricingKeyword>());
				if (savedKeywords != null) {
					for (Keyword kw : savedKeywords) {
						PricingKeyword pkw = new PricingKeyword();
						pkw.setKeywordID(kw.getKeywordId());
						pkw.setWord(kw.getWord());
						pkw.setMatchScope(kw.getMatchscope());
						pkw.setMaxPrice(kw.getMaxPrice());
						pkw.setIsDefaultPrice(0);
						pkw.setMaxMobilePrice(kw.getMaxMobilePrice());
						pkw.setMobileIsDefaultPrice(kw.getMobileIsDefaultPrice());
						ret.getDatas().add(pkw);
					}
				}
				log.info("deleteKeywords costs {} ms", (System.currentTimeMillis() - start));
				ret.setStatus(ret.getDatas().size() > 0);
				result.setResult(ret);
			}
		}, isManual, isSemiauto);

		return result;
	};

	@Data
	public static class DeleteKeywords {
		@JsonProperty("keyword_ids")
		private List<Long> keywordIDs;
	}

	@Data
	@JsonInclude(Include.NON_DEFAULT)
	public static class PricingKeyword implements InvokesChunk.Lengthable {
		@JsonProperty("keywordId")
		private long keywordID;

		private int matchScope = -1;// 1代表精确匹配，2代表子串匹配，4代表广泛匹配
		private String word;
		private int maxPrice = -1;
		private int isDefaultPrice = -1;
		private int maxMobilePrice = -1;
		private int mobileIsDefaultPrice = -1;

		@Override
		public int length() {
			int estimateInitialLength = 50;
			if (this.word != null) {
				return estimateInitialLength + this.word.length();
			}
			return estimateInitialLength + 20;
		}
	}

	@ApiOperation(value = "查看推广组的推荐词添加错误记录中是否和其关键词有重复")
	@RequestMapping(method = RequestMethod.GET, value = "/adgroups/{adgroup_id}/keywords/@duplicate", consumes = "application/json")
	@ResponseBody
	public DuplicatedCheckResult checkDuplicateWithError(@PathVariable("adgroup_id") long adgroupID) {
		return this.checkDuplicateWords(adgroupID, null);
	}

	@ApiOperation(value = "查看请求的推荐词是否和推广组的关键词有重复")
	@RequestMapping(method = RequestMethod.POST, value = "/adgroups/{adgroup_id}/keywords/@duplicate", consumes = "application/json")
	@ResponseBody
	public DuplicatedCheckResult checkDuplicate(@PathVariable("adgroup_id") long adgroupID,
			@RequestBody List<String> checkingWords) {
		Assert.notNull(checkingWords, "checking_words_is_null");
		Assert.isTrue(checkingWords.size() > 0, "no_checking_words");
		return this.checkDuplicateWords(adgroupID, checkingWords);
	}

	private DuplicatedCheckResult checkDuplicateWords(long adgroupID, List<String> checkingWords) {
		DuplicatedCheckResult dto = new DuplicatedCheckResult();

		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		Assert.notNull(adgroup, "adgoup_not_found");

		KeywordExample example = new KeywordExample();
		example.setUserId(adgroup.getUserID());
		example.createCriteria().andAdgroupIdEqualTo(adgroupID);
		List<Keyword> keywords = this.keywordMapper.selectByExample(example);
		if (keywords != null) {
			for (Keyword kw : keywords) {
				dto.addExisting(kw.getWord());
			}
		}

		WordUtils.Builder builder = WordUtils.build();
		SegmentClient.Segments segs = this.segmentClient.segments(dto.getExistings());
		for (SegmentClient.Segment seg : segs.getSegments()) {
			builder.add(seg);
		}

		if (checkingWords == null) {
			checkingWords = new ArrayList<String>();
			List<KeywordAddErrorLog> errorLogs = this.keywordAddErrorLogRepository
					.findByAdgroupIDAndErrorCode(adgroupID, "请勿重复添加关键词");
			for (KeywordAddErrorLog errorLog : errorLogs) {
				if (errorLog != null && errorLog.getWords() != null) {
					for (KeywordAddErrorLog.RecommendWord word : errorLog.getWords()) {
						checkingWords.add(word.getWord());
					}
				}
			}
		}

		segs = this.segmentClient.segments(checkingWords);
		for (SegmentClient.Segment seg : segs.getSegments()) {
			if (builder.contain(seg)) {
				dto.addDuplicate(seg.getWord());
			}
			builder.add(seg);
			dto.addWord(seg.getWord());
		}

		log.debug("words: {}", builder.toString());

		return dto;
	}

	@Data
	@JsonInclude(Include.NON_NULL)
	public static class DuplicatedCheckResult {
		private List<String> duplidates;
		private List<String> words;
		private List<String> existings;

		public void addWord(String w) {
			if (words == null) {
				this.words = new ArrayList<String>();
			}
			this.words.add(w);
		}

		public void addDuplicate(String w) {
			if (duplidates == null) {
				this.duplidates = new ArrayList<String>();
			}
			this.duplidates.add(w);
		}

		public void addExisting(String w) {
			if (existings == null) {
				this.existings = new ArrayList<String>();
			}
			this.existings.add(w);
		}
	}

	@ApiOperation(value = "根据所给的推广计划编号或者推广组编号查找其关键词以及效果数据")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "device", defaultValue = "pc", allowableValues = "pc, mobile", paramType = "query", dataType = "string") })
	@RequestMapping(path = "/users/{user_id}/keywords/@semiauto", method = RequestMethod.GET)
	@ResponseBody
	public List<KeywordDto> find2Keywords(@PathVariable("user_id") Long userID,
			@RequestParam(required = false, name = "adgroup_ids") List<Long> adgroupIDs,
			@RequestParam(required = false, name = "campaign_ids") List<Long> campaignIDs,
			@RequestParam(name = "device", defaultValue = "pc") String device,
			@RequestParam(name = "days", defaultValue = "7") int days) {
		return this.find2Keywords(userID, adgroupIDs, campaignIDs, device, days, null);
	}

	@ApiOperation(value = "根据所给的推广计划编号或者推广组编号查找其关键词以及效果数据")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "device", defaultValue = "pc", allowableValues = "pc, mobile", paramType = "query", dataType = "string") })
	@RequestMapping(path = "/users/{user_id}/keywords/@semiauto", method = RequestMethod.POST)
	@ResponseBody
	public List<KeywordDto> findKeywordsWithCriterias(@PathVariable("user_id") Long userID,
			@RequestParam(required = false, name = "adgroup_ids") List<Long> adgroupIDs,
			@RequestParam(required = false, name = "campaign_ids") List<Long> campaignIDs,
			@RequestParam(name = "device", defaultValue = "pc") String device,
			@RequestParam(name = "days", defaultValue = "7") int days,
			@RequestBody(required = false) List<SearchCriteria> criterias) {
		return this.find2Keywords(userID, adgroupIDs, campaignIDs, device, days, criterias);
	}

	public List<KeywordDto> find2Keywords(long userID, List<Long> adgroupIDs, List<Long> campaignIDs, String device,
			int days, List<SearchCriteria> criterias) {
		long start = System.currentTimeMillis();
		List<KeywordDto> rets = new ArrayList<KeywordDto>();

		if ((adgroupIDs == null || adgroupIDs.size() == 0) && (campaignIDs == null || campaignIDs.size() == 0)) {
			return rets;
		}

		SearchSettings ss = new SearchSettings();
		ss.setOnlineAdgroup(true);
		ss.setCriterias(criterias);
		List<EffectableKeywordDto> keywords = this.keywordService.findKeywords(userID, adgroupIDs, campaignIDs, device,
				days, ss);
		if (keywords.size() == 0) {
			return rets;
		}
		List<Long> keywordIDs = new ArrayList<Long>();
		List<Long> foundAdgroupIDs = new ArrayList<Long>();
		for (EffectableKeywordDto keyword : keywords) {
			keywordIDs.add(keyword.getKeywordID());
			foundAdgroupIDs.add(keyword.getAdgroupID());
			KeywordDto dto = new KeywordDto();
			BeanUtils.copyProperties(keyword, dto);
			rets.add(dto);
		}

		// 是否运行过
		Date today = DateUtils.truncate(new Date(), Calendar.DATE);
		List<KeywordsLog> logs = this.keywordsLogRepository.findByKeywordIDInAndUpdatedTimeAfterAndAction(keywordIDs,
				today, KeywordsLog.Action.SEMIAUTO_EXEC_UPDATE);
		for (KeywordDto dto : rets) {
			if (this.checkFound(logs, dto.getKeywordID())) {
				dto.executed = true;
				log.debug("{} is executed", dto);
			}
		}

		// 是否有收益
		List<RptKeyword> adgroupEffects = this.getAvgCtr(userID, foundAdgroupIDs, days, device);
		for (KeywordDto dto : rets) {
			RptKeyword rpt = this.filterByAdgroupID(adgroupEffects, dto.getAdgroupID());
			float avg = rpt != null ? rpt.getCtr() : 0;
			if (dto.getRoi() > 0 && dto.getClickRatio() > (1.2 * avg)) {
				dto.earning = true;
				log.debug("{} has earning", dto);
			} else {
				dto.earning = false;
			}
		}

		log.info("find2Keywords find {} keywords cost {} ms: campaignIDs({}) - adgroupIDs({})", rets.size(),
				System.currentTimeMillis() - start, campaignIDs, adgroupIDs);
		return rets;
	}

	@ApiOperation(value = "根据所给的推广计划编号或者推广组编号查找其关键词的效果站内排名")
	@RequestMapping(path = "/users/{user_id}/keywords/@position", method = RequestMethod.GET)
	@ResponseBody
	public List<KeywordEffectPositionDto> findKeywordsPosition(@PathVariable("user_id") Long userID,
			@RequestParam(required = false, name = "adgroup_ids") List<Long> adgroupIDs,
			@RequestParam(required = false, name = "campaign_ids") List<Long> campaignIDs,
			@RequestParam(name = "days", defaultValue = "7") int days) {
		Map<Long, KeywordEffectPositionDto> maps = new HashMap<Long, KeywordEffectPositionDto>();

		RptKeywordExample ex = new RptKeywordExample();
		ex.setUserId(userID);
		RptKeywordExample.Criteria cta = this.createSelectCriteria(ex, adgroupIDs, campaignIDs, days);
		cta.andSourceEqualTo((byte) 1);
		List<RptKeyword> rpts = this.rptKeywordMapper.selectReportsTrendByAdgroupId(ex);
		for (RptKeyword rpt : rpts) {
			KeywordEffectPositionDto dto = new KeywordEffectPositionDto();
			dto.setKeywordID(rpt.getKeywordId());
			dto.setPcPosition(rpt.getAvgpos().intValue());
			maps.put(rpt.getKeywordId(), dto);
		}

		ex = new RptKeywordExample();
		ex.setUserId(userID);
		cta = this.createSelectCriteria(ex, adgroupIDs, campaignIDs, days);
		cta.andSourceEqualTo((byte) 4);
		rpts = this.rptKeywordMapper.selectReportsTrendByAdgroupId(ex);
		for (RptKeyword rpt : rpts) {
			KeywordEffectPositionDto dto = maps.get(rpt.getKeywordId());
			if (dto == null) {
				dto = new KeywordEffectPositionDto();
				maps.put(rpt.getKeywordId(), dto);
			}
			dto.setKeywordID(rpt.getKeywordId());
			dto.setMobilePosition(rpt.getAvgpos().intValue());
		}

		List<KeywordEffectPositionDto> dtos = new ArrayList<KeywordEffectPositionDto>();
		for (KeywordEffectPositionDto dto : maps.values()) {
			dtos.add(dto);
		}
		return dtos;
	}

	@ApiOperation(value = "根据所给的推广组编号查询关键词的历史效果数据")
	@RequestMapping(path = "/users/{user_id}/keywords/{keyword_id}/@historyMetric", method = RequestMethod.GET)
	@ResponseBody
	public KeywordHistoryDto findKeywordHistoryMetric(@PathVariable("user_id") long userID,
			@PathVariable("keyword_id") long keywordID,
			@RequestParam(name = "days", defaultValue = "7", required = false) int days,
			@RequestParam(name = "start_date", required = false) String startDate,
			@RequestParam(name = "end_date", required = false) String endDate,
			@RequestParam(name = "device", defaultValue = "all", required = false) String device) {
		KeywordHistoryDto dto = new KeywordHistoryDto();
		List<EffectMetricDto> dtos = new ArrayList<EffectMetricDto>();
		RptKeywordExample ex = new RptKeywordExample();
		ex.setUserId(userID);
		Criteria criteria = ex.createCriteria();
		criteria.andKeywordIdEqualTo(keywordID);

		Date yesterday = DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -1);
		Date start = new Date();
		Date end = null;
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		if (startDate != null && endDate != null) {
			try {
				start = sdf.parse(startDate);
				end = sdf.parse(endDate);
			} catch (ParseException e) {
				log.error("CreativeController reportsTrend:{}", e.getMessage());
			}
			criteria.andDateBetween(start, end);
		} else {
			Date day7Befroe = DateUtils.addDays(DateUtils.truncate(start, Calendar.DATE), -7);
			criteria.andDateBetween(day7Befroe, yesterday);
		}
		List<RptKeyword> rpts = this.rptKeywordMapper.selectByExampleGroupByDate(ex);
		List<RptKeyword> rptSummary = this.rptKeywordMapper.selectByExampleGroupByKeywordID(ex);
		if (rptSummary != null && rptSummary.size() > 0) {
			for (RptKeyword rpt : rptSummary) {
				EffectMetricDto effDto = new EffectMetricDto();
				effDto.filterNull(rpt);
				effDto.from(rpt);
				dtos.add(effDto);
			}
			dto.setSummary(dtos.get(0));
		} else {
			EffectMetricDto effDto = new EffectMetricDto();
			effDto.setClick(0);
			effDto.setImpressions(0);
			effDto.setCtr(0f);
			effDto.setCpc(0f);
			effDto.setSale(0f);
			effDto.setCost(0);
			dto.setSummary(effDto);
		}
		Series series = new Series();
		series.setClick(new ArrayList<Templet>());
		series.setCost(new ArrayList<Templet>());
		series.setCpc(new ArrayList<Templet>());
		series.setCtr(new ArrayList<Templet>());
		series.setImplession(new ArrayList<Templet>());
		series.setRoi(new ArrayList<Templet>());
		series.setSale(new ArrayList<Templet>());
		if (rpts != null && rpts.size() > 0) {
			dto.from(rpts, series);
		} else {
			long max = 0;
			if (StringUtils.isBlank(startDate)) {
				max = 7;
			} else {
				max = (end.getTime() - start.getTime() + 1000000) / (3600 * 24 * 1000) + 1;
			}
			dto.nullFrom(max, series);
		}
		dto.setSeries(series);
		return dto;
	}

	@Data
	public class KeywordHistoryDto {
		private EffectMetricDto summary;
		private Series series;

		public void from(List<RptKeyword> rpts, Series series) {
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
			for (RptKeyword rpt : rpts) {
				Templet implession = new Templet();
				Templet cpc = new Templet();
				Templet roi = new Templet();
				Templet ctr = new Templet();
				Templet click = new Templet();
				Templet cost = new Templet();
				Templet sale = new Templet();
				EffectMetricDto effDto = new EffectMetricDto();
				effDto.filterNull(rpt);
				effDto.from(rpt);
				implession.setDate(sdf.format(rpt.getDate()));
				cpc.setDate(sdf.format(rpt.getDate()));
				roi.setDate(sdf.format(rpt.getDate()));
				ctr.setDate(sdf.format(rpt.getDate()));
				click.setDate(sdf.format(rpt.getDate()));
				cost.setDate(sdf.format(rpt.getDate()));
				sale.setDate(sdf.format(rpt.getDate()));

				implession.setValue(rpt.getImpressions());
				cpc.setValue(rpt.getCpc());
				roi.setValue(rpt.getRoi());
				ctr.setValue(rpt.getCtr());
				click.setValue(rpt.getClick());
				cost.setValue(rpt.getCost());
				sale.setValue(rpt.getSale());
				series.getImplession().add(implession);
				series.getCpc().add(cpc);
				series.getRoi().add(roi);
				series.getCtr().add(ctr);
				series.getClick().add(click);
				series.getCost().add(cost);
				series.getSale().add(sale);
			}
		}

		public void nullFrom(long max, Series series) {
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
			for (int i = 1; i <= max; i++) {
				Templet implession = new Templet();
				Templet cpc = new Templet();
				Templet roi = new Templet();
				Templet ctr = new Templet();
				Templet click = new Templet();
				Templet cost = new Templet();
				Templet sale = new Templet();

				implession.setDate(sdf.format(DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -i)));
				cpc.setDate(sdf.format(DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -i)));
				roi.setDate(sdf.format(DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -i)));
				ctr.setDate(sdf.format(DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -i)));
				click.setDate(sdf.format(DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -i)));
				cost.setDate(sdf.format(DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -i)));
				sale.setDate(sdf.format(DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -i)));
				implession.setValue(0);
				cpc.setValue(0);
				roi.setValue(0);
				ctr.setValue(0);
				click.setValue(0);
				cost.setValue(0);
				sale.setValue(0);
				series.getImplession().add(implession);
				series.getCpc().add(cpc);
				series.getRoi().add(roi);
				series.getCtr().add(ctr);
				series.getClick().add(click);
				series.getCost().add(cost);
				series.getSale().add(sale);
			}

		}
	}

	@Data
	public class Series {
		private List<Templet> implession;
		private List<Templet> cpc;
		private List<Templet> roi;
		private List<Templet> ctr;
		private List<Templet> click;
		private List<Templet> cost;
		private List<Templet> sale;
	}

	@Data
	public class Templet {
		private String date;
		private float value;
	}

	private RptKeywordExample.Criteria createSelectCriteria(RptKeywordExample ex, List<Long> adgroupIDs,
			List<Long> campaignIDs, int days) {
		RptKeywordExample.Criteria cta = ex.createCriteria();
		if (adgroupIDs != null && adgroupIDs.size() > 0) {
			cta.andAdgroupIdIn(adgroupIDs);
		}
		if (campaignIDs != null && campaignIDs.size() > 0) {
			cta.andCampaignIdIn(campaignIDs);
		}
		if (days > 0) {
			Date yesterday = DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -1);
			cta.andDateBetween(DateUtils.addDays(yesterday, -(days - 1)), yesterday);
		}
		return cta;
	}

	private boolean checkFound(List<KeywordsLog> logs, long keywordID) {
		for (KeywordsLog kLog : logs) {
			if (kLog.getKeywordID() == keywordID) {
				return true;
			}
		}
		return false;
	}

	private List<RptKeyword> getAvgCtr(long userID, List<Long> adgroupIDs, int days, String device) {
		RptKeywordExample example = new RptKeywordExample();
		example.setUserId(userID);
		RptKeywordExample.Criteria criteria = this.buildRptKeywordExampleCriteria(example, days, device);
		criteria.andAdgroupIdIn(adgroupIDs);
		return this.rptKeywordMapper.selectReportsTrendByCampaignId(example);
	}

	private RptKeyword filterByAdgroupID(List<RptKeyword> rpts, long adgroupID) {
		for (RptKeyword rpt : rpts) {
			if (rpt.getAdgroupId() == adgroupID) {
				return rpt;
			}
		}
		return null;
	}

	private RptKeywordExample.Criteria buildRptKeywordExampleCriteria(RptKeywordExample example, int days,
			String device) {
		RptKeywordExample.Criteria criteria = example.createCriteria();
		Date yesterday = DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -1);
		criteria.andDateBetween(DateUtils.addDays(yesterday, 1 - days), yesterday);

		if ("mobile".equals(device)) {
			criteria.andSourceBetween((byte) 4, (byte) 5);
		} else {
			criteria.andSourceBetween((byte) 1, (byte) 2);
		}
		return criteria;
	}

	@Data
	public static class KeywordsDto {
		@JsonProperty("adgroup_id")
		private long adgroupID;
		private List<KeywordDto> words;
		private Adgroup adgroup;
	}

	@Data
	@EqualsAndHashCode(callSuper = false)
	@ToString(callSuper = true)
	public static class KeywordDto extends EffectableKeywordDto {
		@JsonProperty("earning")
		private boolean earning;
		@JsonProperty("executed")
		private boolean executed;
	}
}
